/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package weka.attributeSelection.semiAS.semiClusterAS;

import java.util.Enumeration;
import java.util.Vector;
import weka.attributeSelection.semiAS.SemiAttributeSelector;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.CapabilitiesHandler;
import weka.core.Instances;
import weka.core.Option;
import weka.core.TechnicalInformation;
import weka.core.TechnicalInformationHandler;
import weka.core.TechnicalInformation.Field;
import weka.core.TechnicalInformation.Type;
import weka.core.Utils;

/**
 *
 * @author Administrator
 */
public class SemiClusterAS extends SemiAttributeSelector
        implements
        CapabilitiesHandler,
        TechnicalInformationHandler {

    private int m_numCluster;
    private double[][] m_distance;//初始距离矩阵
    private int[] m_indicator;//聚类指示器从1开始
    private double[] m_featClass;//每个特征与类别的相关
    private int[] m_selectAttribute;
    private double[] m_score;
    private Mode m_modeAS;//非0表示sup方法
    private ClusterMethod m_clusterMethod;
    private LabeledDistMethod m_labeledDistMethod;
    private UnlabeledDistMethod m_unlabeledDistMethod;
    private boolean m_isDistWeighted;
    private boolean m_isScoreWeighted;
    private FeatClassMethod m_featClassMethod;
    private DistEvalM m_distEvalM;
    private FeatEvalM m_featEvalM;
    private ClusterForSemiASM m_clusterM;
    private FeatClassEvalM m_featClassEvalM;

    public SemiClusterAS() {
        resetOptions();
    }

    @Override
    public void buildSelector(Instances data) throws Exception {
        // can evaluator handle m_data?
        getCapabilities().testWithFail(data);
        super.buildSelector(data);

        m_selectAttribute = new int[m_numCluster];

        if (m_numCluster == 1 || m_numCluster == m_numAttribs - 1) {
            return;
        }

        if (m_nLabeled == 0) {
            this.setM_lableWeight(0);
            this.setM_modeAS(Mode.SemiAS);
        }

        m_distance = new double[m_numAttribs - 1][m_numAttribs - 1];
        m_indicator = new int[m_numAttribs - 1];
        m_featClass = new double[m_numAttribs - 1];
        m_score = new double[m_numAttribs - 1];

        if (m_nLabeled != 0) {
            m_featClassEvalM = new FeatClassEvalM(m_featClassMethod, m_labeledInstances, m_data);
            calFeatClass();
            setRubbishFeat();
        }
        m_distEvalM = new DistEvalM(m_trainInstances, m_labeledInstances, m_data, m_isDistWeighted, m_modeAS, m_labeleWeight, m_unlabeleWeight, m_labeledDistMethod, m_unlabeledDistMethod);
        calDistance();

    }

    public void startSelector() {

        int maxCluster = m_numAttribs - 1;

        if (m_numCluster == 1 || m_numCluster == maxCluster) {
            for (int i = 0; i < m_numCluster; i++) {
                m_selectAttribute[i] = i + 1;
            }
        } else if (m_numCluster > 1 && m_numCluster < maxCluster) {
            m_clusterM = new ClusterForSemiASM(m_clusterMethod, m_indicator, m_distance, m_numAttribs, m_numCluster);
            m_clusterM.run();
            m_featEvalM = new FeatEvalM(m_indicator, m_distance, m_featClass, m_isScoreWeighted, m_labeleWeight, m_unlabeleWeight);
            calScore();
            selectEachCluster();
        }
        setSelectedResult();

    }

    private void resetOptions() {
        m_numCluster = 2;
        m_labeleWeight = 0.67;
        m_unlabeleWeight = 0.33;
        m_nLabeled = 0;
        m_modeAS = Mode.SemiAS;
        m_clusterMethod = ClusterMethod.KMeans;
        m_labeledDistMethod = LabeledDistMethod.ReliefF;
        m_unlabeledDistMethod = UnlabeledDistMethod.Correlate;
        m_isDistWeighted = true;
        m_isScoreWeighted = true;
        m_featClassMethod = FeatClassMethod.Correlate;
    }

    public Enumeration listOptions() {
        Vector newVector = new Vector(10);

        newVector.addElement(new Option("\tSpecify numCluster value (used in an exp\n"
                + "\tfunction to control how quickly\n"
                + "\tweights for more distant instances\n"
                + "\tdecrease.(Default = 2)\n"
                + "\t", "K", 2, "-K <numCluster>"));
        newVector.addElement(new Option("\tWeight percent of labled distance (used\n"
                + "\tto estimate attribute relevances\n"
                + "\t(Default = 0.5).", "W", 50, "-W <lableWeight>"));
        newVector.addElement(new Option("\tNumber of split labled instance (used\n"
                + "\tto set the proportion of unlabled distance\n"
                + "\t(Default = 0).", "N", 0, "-N <nLabled>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "S", 0, "-S <m_supMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "M", 1, "-M <m_clusterMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "L", 2, "-L <m_labeledDistMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "U", 0, "-U <m_unlabeledDistMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "C", 0, "-C <m_featClassMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "E", 1, "-E <m_unlabeledDistMethod>"));
        newVector.addElement(new Option("\ttSpecify wheather supMethod(used\n"
                + "\tto specify wheather supMethod\n"
                + "\t(Default = 0).", "D", 1, "-D <m_featClassMethod>"));

        return newVector.elements();
    }

    public double getM_lableWeight() {
        return m_labeleWeight;
    }

    public void setM_lableWeight(double m_lableWeight) {
        this.m_labeleWeight = m_lableWeight;
        this.m_unlabeleWeight = 1 - m_lableWeight;
    }

    public int getM_numCluster() {
        return m_numCluster;
    }

    public void setM_numCluster(int m_numCluster) throws Exception {
        this.m_numCluster = m_numCluster;
    }

    public void setM_nLabled(int n) {
        this.m_nLabeled = n;
    }

    public int getM_nLabled() {
        return m_nLabeled;
    }

    public Mode getM_modeAS() {
        return m_modeAS;
    }

    public void setM_modeAS(Mode m_modeAS) {
        this.m_modeAS = m_modeAS;
    }

    public ClusterMethod getM_clusterMethod() {
        return m_clusterMethod;
    }

    public void setM_clusterMethod(ClusterMethod m_clusterMethod) {
        this.m_clusterMethod = m_clusterMethod;
    }

    public FeatClassMethod getM_featClassMethod() {
        return m_featClassMethod;
    }

    public void setM_featClassMethod(FeatClassMethod m_featClassMethod) {
        this.m_featClassMethod = m_featClassMethod;
    }

    public boolean getM_isDistWeighted() {
        return m_isDistWeighted;
    }

    public void setM_isDistWeighted(boolean m_isDistWeighted) {
        this.m_isDistWeighted = m_isDistWeighted;
    }

    public boolean getM_isScoreWeighted() {
        return m_isScoreWeighted;
    }

    public void setM_isScoreWeighted(boolean m_isScoreWeighted) {
        this.m_isScoreWeighted = m_isScoreWeighted;
    }

    public LabeledDistMethod getM_labeledDistMethod() {
        return m_labeledDistMethod;
    }

    public void setM_labeledDistMethod(LabeledDistMethod m_labeledDistMethod) {
        this.m_labeledDistMethod = m_labeledDistMethod;
    }

    public UnlabeledDistMethod getM_unlabeledDistMethod() {
        return m_unlabeledDistMethod;
    }

    public void setM_unlabeledDistMethod(UnlabeledDistMethod m_unlabeledDistMethod) {
        this.m_unlabeledDistMethod = m_unlabeledDistMethod;
    }

    public void setOptions(String[] options) throws Exception {
        String optionString;
        String[] optionsTmp = (String[]) options.clone();
        resetOptions();

        if (Utils.getFlag('K', optionsTmp)) {
            optionString = Utils.getOption('K', options); //m_numCluster.
            setM_numCluster(Integer.parseInt(optionString));
        }

        if (Utils.getFlag('W', optionsTmp)) {
            optionString = Utils.getOption('W', options); //m_labelWeight
            setM_lableWeight(Double.parseDouble(optionString) / 100);
        }

        if (Utils.getFlag('N', optionsTmp)) {
            optionString = Utils.getOption('N', options); //m_nLabled
            setM_nLabled(Integer.parseInt(optionString));
        }

        if (Utils.getFlag('S', optionsTmp)) {
            optionString = Utils.getOption('S', options); //m_modeAS
            setM_modeAS(Enum.valueOf(Mode.class, optionString));
        }

        if (Utils.getFlag('M', optionsTmp)) {
            optionString = Utils.getOption('M', options); //m_clusterMethod
            setM_clusterMethod(Enum.valueOf(ClusterMethod.class, optionString));
        }

        if (Utils.getFlag('L', optionsTmp)) {
            optionString = Utils.getOption('L', options); //m_labeledDistMethod
            setM_labeledDistMethod(Enum.valueOf(LabeledDistMethod.class, optionString));
        }

        if (Utils.getFlag('U', optionsTmp)) {
            optionString = Utils.getOption('U', options); //m_unlabeledDistMethod
            setM_unlabeledDistMethod(Enum.valueOf(UnlabeledDistMethod.class, optionString));
        }

        if (Utils.getFlag('C', optionsTmp)) {
            optionString = Utils.getOption('C', options); //m_featClassMethod
            setM_featClassMethod(Enum.valueOf(FeatClassMethod.class, optionString));
        }

        if (Utils.getFlag('E', optionsTmp)) {
            optionString = Utils.getOption('E', options); //m_isScoreWeighted
            setM_isScoreWeighted(Boolean.parseBoolean(optionString));
        }

        if (Utils.getFlag('D', optionsTmp)) {
            optionString = Utils.getOption('D', options); //m_isDistWeighted
            setM_isScoreWeighted(Boolean.parseBoolean(optionString));
        }
    }

    public String[] getOptions() {
        String[] options = new String[20];
        int current = 0;

        options[current++] = "-K";
        options[current++] = "" + this.getM_numCluster(); //m_numCluster

        options[current++] = "-W";
        options[current++] = "" + this.getM_lableWeight(); //m_labeleWeight

        options[current++] = "-N";
        options[current++] = "" + this.getM_nLabled(); //m_nLabeled

        options[current++] = "-S";
        options[current++] = "" + this.getM_modeAS(); //m_modeAS

        options[current++] = "-M";
        options[current++] = "" + this.getM_clusterMethod(); //m_clusterMethod

        options[current++] = "-L";
        options[current++] = "" + this.getM_labeledDistMethod(); //m_labeledDistMethod

        options[current++] = "-U";
        options[current++] = "" + this.getM_unlabeledDistMethod(); //m_unlabeledDistMethod

        options[current++] = "-C";
        options[current++] = "" + this.getM_featClassMethod(); //m_featClassMethod

        options[current++] = "-E";
        options[current++] = "" + this.getM_isScoreWeighted(); //m_isScoreWeighted

        options[current++] = "-D";
        options[current++] = "" + this.getM_isDistWeighted(); //m_isDistWeighted

        return options;
    }

    public TechnicalInformation getTechnicalInformation() {
        TechnicalInformation result;
        result = new TechnicalInformation(Type.INPROCEEDINGS);
        result.setValue(Field.AUTHOR, "Ianisse Quinzán José M. Sotoca Filiberto Pla");
        result.setValue(Field.TITLE, "Clustering-based Feature Selection in Semi-supervised Problems");
        result.setValue(Field.YEAR, "9");
        result.setValue(Field.PAGES, "1151–1158");
        result.setValue(Field.PUBLISHER, "ISDA");
        return result;
    }

    @Override
    public Capabilities getCapabilities() {
        Capabilities result = new Capabilities(this);

        // attributes
        result.enable(Capability.NOMINAL_ATTRIBUTES);
        result.enable(Capability.NUMERIC_ATTRIBUTES);
        result.enable(Capability.DATE_ATTRIBUTES);
        result.enable(Capability.MISSING_VALUES);

        // class
        result.enable(Capability.NOMINAL_CLASS);
        result.enable(Capability.NUMERIC_CLASS);
        result.enable(Capability.DATE_CLASS);
        result.enable(Capability.MISSING_CLASS_VALUES);

        return result;
    }

    private void calFeatClass() {
        for (int i = 0; i < m_numAttribs - 1; i++) {
            m_featClass[i] = m_featClassEvalM.getScore(i);
        }
    }

    private void setRubbishFeat() {
        for (int i = 0; i < m_numAttribs - 1; i++) {
            if (m_featClass[i] == 0) {
                m_indicator[i] = -1;
            }
        }
    }

    private void calScore() {
        for (int i = 0; i < m_numAttribs - 1; i++) {
            if (m_indicator[i] == -1) {
                continue;
            }
            m_score[i] = m_featEvalM.getScore(i);
        }
    }

    private void selectEachCluster() {
        boolean[] flag = new boolean[m_numCluster];//标记是否扫描到某一聚类，以便找出该聚类最大值
        double[] maxScore = new double[m_numCluster];//每一聚类与类别的最大相关系数
        for (int i = 0; i < m_numAttribs - 1; i++) {
            if (m_indicator[i] == -1) {
                continue;
            }
            int index = m_indicator[i] - 1;
            if (!flag[index]) {                       //找到该聚类的第一个元素作为其初始最大值
                maxScore[index] = m_score[i];
                m_selectAttribute[index] = i + 1;
                flag[index] = true;
            } else if (m_score[i] > maxScore[index]) {//更新该聚类的最大值
                maxScore[index] = m_score[i];
                m_selectAttribute[index] = i + 1;
            }
        }
    }

    private void calDistance() {
        for (int i = 0; i < m_numAttribs - 1; i++) {
            for (int j = 0; j < i; j++) {
                m_distance[i][j] = m_distance[j][i] = m_distEvalM.getDistance(i, j);
            }
        }
    }

    private void setSelectedResult() {
        int[] sortedArray = new int[m_numCluster];

        if (m_numCluster > 2 && m_numCluster < m_numAttribs - 1) {
            sortedArray = Utils.sort(m_selectAttribute);
        } else {
            for (int i = 0; i < m_numCluster; i++) {
                sortedArray[i] = i;
            }
        }
        for (int i = 0; i < m_numCluster; i++) {
            int index = sortedArray[i];

            m_selectedResult = m_selectedResult + m_selectAttribute[index] + " ";
            m_reducedIndexSet[i] = m_selectAttribute[index] - 1;
        }
        m_selectedResult = m_selectedResult + ":" + m_numCluster;
    }

    @Override
    protected void initReducedIndexSet() {
        m_reducedIndexSet = new int[m_numCluster + 1];
        m_reducedIndexSet[m_numCluster] = m_classIndex;
    }

    public static void main(String[] args) {

        new SemiClusterAS().runSemiAttributeSelector(args);
    }
}
